home *** CD-ROM | disk | FTP | other *** search
Text File | 2007-10-18 | 44.8 KB | 1,363 lines |
- // BEGIN FLOCK GPL
- //
- // Copyright Flock Inc. 2005-2007
- // http://flock.com
- //
- // This file may be used under the terms of of the
- // GNU General Public License Version 2 or later (the "GPL"),
- // http://www.gnu.org/licenses/gpl.html
- //
- // Software distributed under the License is distributed on an "AS IS" basis,
- // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- // for the specific language governing rights and limitations under the
- // License.
- //
- // END FLOCK GPL
-
- const CC = Components.classes;
- const CI = Components.interfaces;
- const CR = Components.results;
- const CU = Components.utils;
-
- CU.import("resource:///modules/FlockXPCOMUtils.jsm");
- FlockXPCOMUtils.debug = false;
-
- CU.import("resource:///modules/FlockScheduler.jsm");
- CU.import("resource:///modules/FlockSvcUtils.jsm");
- CU.import("resource://gre/modules/JSON.jsm");
-
- const MODULE_NAME = "Twitter";
- const CLASS_NAME = "Flock Twitter Service";
- const CLASS_SHORT_NAME = "twitter";
- const CLASS_TITLE = "Twitter";
- const CLASS_ID = Components.ID("{535BFF20-9154-11DB-B606-0800200C9A66}");
- const CONTRACT_ID = "@flock.com/people/twitter;1";
-
- const FLOCK_RICH_DND_CONTRACTID = "@flock.com/rich-dnd-service;1";
-
- const SERVICE_ENABLED_PREF = "flock.service.twitter.enabled";
-
- const FAVICON = "chrome://flock/content/services/twitter/favicon.png";
-
- // From nsIXMLHttpRequest.idl
- const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
-
- const HTTP_CODE_OK = 200;
- const HTTP_CODE_FOUND = 302;
-
- // The delay between two refreshes when the sidebar is closed (in seconds)
- const REFRESH_INTERVAL = 1800; // seconds == 30 minutes
- // The delay between two refreshes when the sidebar is open (in seconds)
- const SHORT_INTERVAL = 300; // seconds == 5 minutes
-
- const TWITTER_URL = "http://www.twitter.com/"
-
- // This is a workaround for the 401 errors we're getting when attempting to
- // authenticate to Twitter's API. If we get a 401, we'll silently retry up to
- // this number of times before giving up and showing the user an error.
- const TWITTER_MAX_AUTH_ATTEMPTS = 5;
-
- // Twitter returns friends in pages of 100.
- const TWITTER_FRIENDS_PAGE_SIZE = 100;
-
- // Twitter returns messages in pages of 20.
- const TWITTER_MESSAGES_PAGE_SIZE = 20;
-
- // Twitter API documentation specifies the maximum length of a status message.
- const TWITTER_MAX_STATUS_LENGTH = 140;
-
- const TWITTER_PROPERTIES = "chrome://flock/locale/services/twitter.properties";
-
- var gApi = null;
-
- /*************************************************************************
- * Component: flockTwitterService
- *************************************************************************/
- function flockTwitterService() {
- this._init();
-
- FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAccount");
- FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getAccounts");
- FlockSvcUtils.flockIWebService.addDefaultMethod(this, "logout");
-
- FlockSvcUtils.flockIManageableWebService
- .addDefaultMethod(this, "docRepresentsSuccessfulLogin");
- FlockSvcUtils.flockIManageableWebService
- .addDefaultMethod(this, "getAccountIDFromDocument");
- FlockSvcUtils.flockIManageableWebService
- .addDefaultMethod(this, "getCredentialsFromForm");
- FlockSvcUtils.flockIManageableWebService
- .addDefaultMethod(this, "ownsDocument");
- FlockSvcUtils.flockIManageableWebService
- .addDefaultMethod(this, "ownsLoginForm");
-
- FlockSvcUtils.flockIRichContentDropHandler
- .addDefaultMethod(this, "_handleTextareaDrop");
- }
-
-
- /*************************************************************************
- * flockTwitterService: XPCOM Component Creation
- *************************************************************************/
-
- flockTwitterService.prototype = new FlockXPCOMUtils.genericComponent(
- CLASS_NAME,
- CLASS_ID,
- CONTRACT_ID,
- flockTwitterService,
- CI.nsIClassInfo.SINGLETON,
- [
- CI.nsIObserver,
- CI.flockIWebService,
- CI.flockIManageableWebService,
- CI.flockIPollingService,
- CI.flockISocialWebService,
- CI.flockIRichContentDropHandler
- ]
- );
-
- // FlockXPCOMUtils.genericModule() categories
- flockTwitterService.prototype._xpcom_categories = [
- { category: "wsm-startup" },
- { category: "flockWebService", entry: CLASS_SHORT_NAME },
- { category: "flockRichContentHandler", entry: CLASS_SHORT_NAME }
- ];
-
-
- /*************************************************************************
- * flockTwitterService: Private Data and Functions
- *************************************************************************/
-
- // Member variables.
- flockTwitterService.prototype._init =
- function fts_init() {
- FlockSvcUtils.getLogger(this);
- this._logger.debug(".init()");
-
- // Determine whether this service has been disabled, and unregister if so.
- var prefService = CC["@mozilla.org/preferences-service;1"]
- .getService(CI.nsIPrefBranch);
- if (prefService.getPrefType(SERVICE_ENABLED_PREF) &&
- !prefService.getBoolPref(SERVICE_ENABLED_PREF))
- {
- this._logger.debug(SERVICE_ENABLED_PREF + " is FALSE! Not initializing.");
- this.deleteCategories();
- return;
- }
-
- var profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
- var evtID = profiler.profileEventStart("twitter-init");
-
- var obs = CC["@mozilla.org/observer-service;1"]
- .getService(CI.nsIObserverService);
- obs.addObserver(this, "xpcom-shutdown", false);
-
- this._accountClassCtor = flockTwitterAccount;
-
- FlockSvcUtils.getCoop(this);
-
- this._baseUrn = "urn:twitter";
- this._serviceUrn = this._baseUrn + ":service";
-
- if (this._coop.Service.exists(this._serviceUrn)) {
- this._c_svc = this._coop.get(this._serviceUrn);
- } else {
- this._c_svc = new this._coop.Service(this._serviceUrn, {
- name: CLASS_SHORT_NAME,
- desc: CLASS_TITLE
- });
- }
- this._c_svc.serviceId = CONTRACT_ID;
- this._c_svc.logoutOption = true;
-
- // Note that using FlockSvcUtils.getWD here adds the "_WebDetective"
- // property to the service.
- this._c_svc.domains = FlockSvcUtils.getWD(this)
- .getString("twitter", "domains", "twitter.com");
- this._c_svc.loginURL = FlockSvcUtils.getWD(this)
- .getString("twitter",
- "userlogin",
- "https://twitter.com/login/");
-
- // Initialize API
- gApi = new flockTwitterAPI();
-
- // Update auth states
- try {
- if (this._WebDetective.detectCookies("twitter", "loggedout", null)) {
- this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
- }
- } catch (ex) {
- this._logger.error("ERROR updating auth states for Twitter: " + ex);
- }
-
- profiler.profileEventEnd(evtID, "");
- }
-
-
- flockTwitterService.prototype._lightPeopleIcon =
- function flockTwitterService__lightPeopleIcon() {
- this._logger.debug("._lightPeopleIcon()");
- var obs = CC["@mozilla.org/observer-service;1"]
- .getService(CI.nsIObserverService);
- obs.notifyObservers(null, "new-people-notification", null);
- }
-
-
- flockTwitterService.prototype._refreshAccount =
- function fts__refreshAccount(aUrn, aListener) {
- var inst = this;
- var refreshItem = this._coop.get(aUrn);
- var lastUpdate = refreshItem.lastUpdateDate;
- var numberOfAnswers = 0;
-
- // Ensures that both listeners have completed before sending our caller a
- // response. Also sets the next refresh time to be soon if the people
- // sidebar is open.
- function onIndividualSuccess() {
- numberOfAnswers++;
- if (numberOfAnswers >= 3) {
- if (inst._acUtils.isPeopleSidebarOpen()) {
- refreshItem.nextRefresh = new Date(Date.now() + SHORT_INTERVAL * 1000);
- }
- if (aListener) {
- aListener.onResult();
- }
- }
- }
-
- /**
- * {
- * "status": {
- * "created_at": "Thu May 17 19:19:39 +0000 2007",
- * "text": "attending the full staff meeting",
- * "id": 67511202
- * },
- * "url": null,
- * "followers_count": 0,
- * "friends_count": 0,
- * "profile_background_color": "9ae4e8",
- * "name": "Matthew Willis",
- * "favourites_count": 0,
- * "profile_text_color": "000000",
- * "statuses_count": 1,
- * "profile_link_color": "0000ff",
- * "description": null,
- * "profile_sidebar_fill_color": "e0ff92",
- * "location": null,
- * "profile_image_url": "http:\/\/assets0.twitter.com\/images\/default_image.gif?1189634879",
- * "id": 6117302,
- * "utc_offset": -14400,
- * "profile_sidebar_border_color": "87bc44",
- * "screen_name": "lilmatt",
- * "protected": false
- * }
- */
-
- // This listener handles getting the account owner's information.
- var userShowListener = {
- onSuccess: function userShow_onSuccess(aResult) {
- inst._logger.debug("Success for userShow");
-
- if (aResult.name) {
- inst._logger.debug("userShow: name: " + aResult.name);
- refreshItem.name = aResult.name;
- }
-
- if (aResult.screen_name) {
- inst._logger.debug("userShow: screen_name: " + aResult.screen_name);
- refreshItem.screenName = aResult.screen_name;
- refreshItem.URL = inst._WebDetective
- .getString(CLASS_SHORT_NAME, "userprofile", null)
- .replace("%accountid%", aResult.screen_name);
- }
-
- if (aResult.profile_image_url) {
- inst._logger.debug("userShow: avatar: " + aResult.profile_image_url);
- // If avatar returned is the default image, set coop.Account.avatar
- // to null and let the people sidebar code set the Flock common image.
- if (inst._hasDefaultAvatar(aResult.profile_image_url)) {
- inst._logger.debug("No avatar for account. Setting to null.");
- refreshItem.avatar = null;
- } else {
- refreshItem.avatar = aResult.profile_image_url;
- }
- }
-
- if (aResult.status &&
- aResult.status.text &&
- aResult.status.text != refreshItem.statusMessage)
- {
- inst._logger.debug("userShow: status: " + aResult.status.text);
- refreshItem.statusMessage = aResult.status.text;
-
- var dateString = aResult.status.created_at;
- refreshItem.lastProfileUpdate = inst._parseTwitterDate(dateString);
- }
-
- onIndividualSuccess();
- },
- onError: function userShow_onError(aError) {
- inst._logger.error("Error on userShow");
- aListener.onError(aError);
- }
- };
-
- // This listener handles getting the user's friends' information.
- var friendsListener = {
- onSuccess: function friends_onSuccess(aResult) {
- inst._logger.debug("friendsListener onSuccess");
-
- // Now that we have all the results we need, update the RDF.
- function myWorker(aShouldYield) {
- // ADD or update existing people
- for (var uid in aResult) {
- inst._addPerson(aResult[uid], refreshItem);
- if (aShouldYield()) {
- yield;
- }
- }
- // REMOVE locally people removed on the server
- var localEnum = refreshItem.friendsList.children.enumerate();
- while (localEnum.hasMoreElements()) {
- var identity = localEnum.getNext();
- if (!aResult[identity.accountId]) {
- inst._logger.info("Friend " + identity.accountId
- + " has been deleted on the server");
- refreshItem.friendsList.children.remove(identity);
- identity.destroy();
- }
- }
- onIndividualSuccess();
- }
- FlockScheduler.schedule(null, 0.05, 10, myWorker);
- },
- onError: function friends_onError(aError) {
- inst._logger.error("friendsListener onError");
- aListener.onError(aError);
- }
- };
-
- // This listener handles getting a count of the user's direct messages.
- var messageCountListener = {
- onSuccess: function messages_onSuccess(aResult) {
- inst._logger.debug("messageCountListener onSuccess");
- inst._logger.debug("Message count: " + aResult);
- refreshItem.accountMessages = aResult;
-
- // Trigger the people icon highlight if the user has messages
- if (aResult > 0) {
- inst._lightPeopleIcon();
- }
-
- onIndividualSuccess();
- },
- onError: function messages_onError(aError) {
- inst._logger.error("messageCountListener onError");
- aListener.onError(aError);
- }
- };
-
- gApi.userShow(refreshItem.accountId, userShowListener);
- gApi.getFriendsStatus(null, friendsListener);
- gApi.getTotalMessageCount(messageCountListener);
- }
-
-
- flockTwitterService.prototype._addPerson =
- function fts__addPerson(aPerson, aCoopAccount) {
- // We include the current accountId in the identity urn to prevent friend
- // collisions if multiple service accounts have the same friend.
- var identityUrn = this._getIdentityUrn(aCoopAccount.accountId, aPerson.id);
- var updating = this._coop.Identity.exists(identityUrn);
- var identity;
-
- var lastUpdate = this._parseTwitterDate(aPerson.status.created_at);
- this._logger.debug("Adding person: " + aPerson.id + " - " + aPerson.name);
-
- // XXX: Since Twitter only timestamps status changes, we need to add
- // property-wise profile comparison here.
- var lastUpdateType = "status";
-
- var avatarUrl = null;
- if (!this._hasDefaultAvatar(aPerson.profile_image_url)) {
- avatarUrl = aPerson.profile_image_url;
- }
-
- if (updating) {
- // Update data of the coop.Identity object
- identity = this._coop.get(identityUrn);
- if (lastUpdate > identity.lastUpdate) {
- identity.name = aPerson.name;
- identity.avatar = avatarUrl;
- identity.screenName = aPerson.screen_name;
- identity.statusMessage = aPerson.status.text;
- identity.lastUpdate = lastUpdate;
- identity.lastUpdateType = lastUpdateType;
- }
- } else {
- identity = new this._coop.Identity(
- identityUrn,
- {
- name: aPerson.name,
- serviceId: this.contractId,
- accountId: aPerson.id,
- avatar: avatarUrl,
- screenName: aPerson.screen_name,
- statusMessage: aPerson.status.text,
- lastUpdate: lastUpdate,
- lastUpdateType: lastUpdateType
- }
- );
- aCoopAccount.friendsList.children.add(identity);
- }
- }
-
-
- flockTwitterService.prototype._getIdentityUrn =
- function fts__getIdentityUrn(aAccountId, aUid) {
- var prefix = "urn:flock:identity:" + CLASS_SHORT_NAME;
- return prefix + aAccountId + ":" + aUid;
- }
-
-
- /**
- * Helper function to parse Twitter's date string into seconds since epoch.
- * @param aDateString A string formatted as: Wed Jan 31 00:16:35 +0000 2007
- * @return The number of seconds since the epoch.
- */
- flockTwitterService.prototype._parseTwitterDate =
- function fts__parseTwitterDate(aDateString) {
- this._logger.debug("_parseTwitterDate: in: " + aDateString);
- if (!aDateString) {
- return 0;
- }
- // Date.parse() returns milliseconds since epoch. Divide by 1000 for seconds.
- return (Date.parse(aDateString) / 1000);
- }
-
-
- /**
- * Helper function to determine if the user has customized their avatar based
- * on the passed in URL.
- * @param aUrl A string containing the contents of the Twitter user's
- * "profile_image_url" property.
- * @return true if the user is still using the default avatar, else false
- */
- flockTwitterService.prototype._hasDefaultAvatar =
- function fts_hasDefaultAvatar(aUrl) {
- this._logger.debug("_hasDefaultAvatar(" + aUrl + ")");
- var defaultUrl = this._WebDetective.getString("twitter", "noAvatar", "");
- return (aUrl.indexOf(defaultUrl) != -1);
- }
-
-
- /*************************************************************************
- * flockTwitterService: flockIWebService Implementation
- *************************************************************************/
-
- // readonly attribute AString contractId;
- // ALMOST duplicated by nsIClassInfo::contractID
- // with different case: contractId != contractID
- flockTwitterService.prototype.contractId = CONTRACT_ID;
-
- // readonly attribute AString icon;
- flockTwitterService.prototype.icon = FAVICON;
-
- // readonly attribute boolean needPassword;
- flockTwitterService.prototype.needPassword = true;
-
- // readonly attribute AString shortName;
- flockTwitterService.prototype.shortName = CLASS_SHORT_NAME;
-
- // readonly attribute long status;
- flockTwitterService.prototype.status = CI.flockIWebService.STATUS_UNKNOWN;
-
- // readonly attribute AString title;
- flockTwitterService.prototype.title = CLASS_TITLE;
-
- // readonly attribute AString url;
- flockTwitterService.prototype.url = TWITTER_URL;
-
- // readonly attribute AString urn;
- flockTwitterService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";
-
- /**
- * @see flockIWebService#addAccountById
- */
- flockTwitterService.prototype.addAccountById =
- function fts_addAccountById(aAccountId, aIsTransient, aListener) {
- this._logger.debug(".addAccountById('"
- + aAccountId + "', " + aIsTransient + ", aListener)");
-
- if (!aAccountId) {
- aListener.onError();
- return;
- }
-
- var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
- var name = (pw) ? pw.user : aAccountId;
-
- var accountUrn = "urn:flock:" + this.shortName + ":account:" + aAccountId;
- var account = new this._coop.Account(
- accountUrn, {
- name: name,
- serviceId: this.contractId,
- service: this._c_svc,
- accountId: aAccountId,
- isPollable: false,
- isTransient: aIsTransient,
- URL: this._WebDetective.getString(CLASS_SHORT_NAME, "userprofile", null)
- .replace("%accountid%", aAccountId),
- refreshInterval: REFRESH_INTERVAL,
- favicon: FAVICON
- });
- this._coop.accounts_root.children.add(account);
-
- var friendsListUrn = accountUrn + ":friends";
- var friendsList = new this._coop.FriendsList(
- friendsListUrn,
- {
- account: account
- });
- account.friendsList = friendsList;
-
- // Instantiate account component
- var acct = this.getAccount(account.id());
- if (aListener) {
- aListener.onSuccess(acct, "addAccount");
- }
- return acct;
- }
-
- /**
- * @see flockIWebService#removeAccount
- */
- flockTwitterService.prototype.removeAccount =
- function fts_removeAccount(aUrn) {
- this._logger.debug(".removeAccount(" + aUrn + ")");
- this._acUtils.removeAccount(aUrn);
- }
-
-
- /*************************************************************************
- * flockTwitterService: flockIManageableWebService Implementation
- *************************************************************************/
-
- // DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
- // DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
- // DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
- // DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
- // DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);
-
- /**
- * void updateAccountStatusFromDocument(in nsIDOMHTMLDocument aDocument);
- *
- * @see flockIManageableWebService#updateAccountStatusFromDocument
- */
- flockTwitterService.prototype.updateAccountStatusFromDocument =
- function fts_updateAccountStatusFromDocument(aDocument) {
- this._logger.debug(".updateAccountStatusFromDocument(aDocument)");
- if (this._WebDetective.detect(this.shortName, "loggedout", aDocument, null) ||
- this._WebDetective.detectCookies(this.shortName, "loggedout", null))
- {
- this._logger.debug("We're logged out!");
- this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
- } else if (this._WebDetective.detect(this.shortName,
- "loggedin",
- aDocument,
- null))
- {
- this._logger.debug("We're NOT logged out! Let's try to get some creds!");
- var results = FlockSvcUtils.newResults();
- if (this._WebDetective.detect(this.shortName,
- "accountinfo",
- aDocument,
- results))
- {
- var acctId = results.getPropertyAsAString("accountid");
- var acctUrn = this._acUtils.getAccountURNById(this.urn, acctId);
- var acct = this._coop.get(acctUrn);
- if (!acct.isAuthenticated) {
- acct.isAuthenticated = true;
- }
- }
- } else {
- this._logger.debug("We don't match 'loggedout' or 'logged in'");
- }
- }
-
- /*************************************************************************
- * flockTwitterService: flockISocialWebService implementation
- *************************************************************************/
-
- flockTwitterService.prototype.maxStatusLength = TWITTER_MAX_STATUS_LENGTH;
-
-
- /*************************************************************************
- * flockTwitterService: flockIPollingService Implementation
- *************************************************************************/
- /**
- * @see flockIPollingService#refresh
- */
- flockTwitterService.prototype.refresh =
- function fts_refresh(aUrn, aListener) {
- this._logger.debug(".refresh(" + aUrn + ")");
- var refreshItem = this._coop.get(aUrn);
-
- if (refreshItem instanceof this._coop.Account) {
- this._logger.debug("refreshing an Account");
- if (refreshItem.isAuthenticated) {
- this._refreshAccount(aUrn, aListener);
- } else {
- // If the user is not logged in, return a success without
- // refreshing anything
- aListener.onResult();
- }
- } else {
- this._logger.error("can't refresh " + aUrn + " (unsupported type)");
- aListener.onError(null);
- }
- }
-
-
- /**************************************************************************
- * flockTwitterService: nsIObserver Implementation
- **************************************************************************/
-
- flockTwitterService.prototype.observe =
- function fts_observe(aSubject, aTopic, aState) {
- switch (aTopic) {
- case "xpcom-shutdown":
- var obs = CC["@mozilla.org/observer-service;1"]
- .getService(CI.nsIObserverService);
- obs.removeObserver(this, "xpcom-shutdown");
- break;
- }
- }
-
-
- /**************************************************************************
- * flockTwitterService: flockIRichContentDropHandler Implementation
- **************************************************************************/
-
- flockTwitterService.prototype.handleDrop =
- function fts_handleDrop(aFlavours, aTextarea) {
- var inst = this;
- var dropCallback = function facebook_dropCallback(aFlav) {
- // Get URL from dropped text
- var dataObj = {}, len = {};
- aFlavours.getTransferData(aFlav, dataObj, len);
- var text = dataObj.value.QueryInterface(CI.nsISupportsString).data;
- var textParts = text.split(": ");
- var url = (textParts.length == 2) ? textParts[1] : text;
-
- // Find position
- var caretPos = aTextarea.selectionEnd;
- var currentValue = aTextarea.value;
-
- // Add a trailing space so that we don't mangle the url
- var nextChar = currentValue.charAt(caretPos);
- var trailingSpace = ((nextChar == "") ||
- (nextChar == " ") ||
- (nextChar == "\n"))
- ? ""
- : " ";
-
- // Put it all together to drop the text into the selection. Note: no
- // breadcrumb due to twitter length constraint.
- aTextarea.value = currentValue.substring(0, caretPos)
- + url
- + trailingSpace
- + currentValue.substring(caretPos);
- };
-
- return this._handleTextareaDrop(CLASS_SHORT_NAME, this._c_svc.domains,
- aTextarea, dropCallback);
- }
-
-
- /*************************************************************************
- * Component: flockTwitterAPI
- *************************************************************************/
- function flockTwitterAPI() {
- FlockSvcUtils.getLogger(this);
- this._logger.init("twitterAPI");
-
- FlockSvcUtils.getACUtils(this);
- FlockSvcUtils.getCoop(this);
-
- this._logger.debug("constructor");
- }
-
-
- /*************************************************************************
- * flockTwitterAPI: XPCOM Component Creation
- *************************************************************************/
-
- // Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
- // but do NOT add this component to the list of constructors passed to
- // FlockXPCOMUtils.genericModule().
- flockTwitterAPI.prototype = new FlockXPCOMUtils.genericComponent(
- CLASS_NAME + " API",
- "",
- "",
- flockTwitterAPI,
- 0,
- []
- );
-
-
- /*************************************************************************
- * flockTwitterAPI: Private data and functions
- *************************************************************************/
- /**
- * Actually do the API call.
- * @param aListener
- * @param aMethod
- * @param aParams
- * @param aRequestType "GET" or "POST"
- * @param aPostVars Array of JS objects to include in the POST body
- * @param aCount A variable to increment when a 401 error occurs before
- * trying again. This is handled internally by this
- * function. External callers should set this to null.
- */
- flockTwitterAPI.prototype._call =
- function api__call(aListener, aMethod, aParams, aRequestType, aPostVars,
- aCount)
- {
- var requestType = aRequestType.toUpperCase();
- var responseFormat = ".json";
- var url = "https://twitter.com/" + aMethod + responseFormat;
-
- var idx = 0;
- for (var p in aParams) {
- // Only use "?" for the first param. Use "&" after.
- url += (idx == 0) ? "?" : "&";
- url += p + "=" + aParams[p];
- idx++;
- }
-
- this._logger.debug("._call() url: " + url);
-
- var req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
- .createInstance(CI.nsIXMLHttpRequest)
- .QueryInterface(CI.nsIJSXMLHttpRequest);
-
- // Don't pop nsIAuthPrompts if auth fails
- req.backgroundRequest = true;
-
- var coopAccountUrn = this._acUtils
- .getFirstAuthenticatedAccountForService(CONTRACT_ID);
- var coopAccount = this._coop.get(coopAccountUrn);
- var passwordUrn = "urn:twitter:service:" + coopAccount.accountId;
- var creds = this._acUtils.getPassword(passwordUrn);
-
- req.open(requestType, url, true, creds.user, creds.password);
-
- if (requestType == "POST") {
- req.setRequestHeader("Content-Type",
- "application/x-www-form-urlencoded; charset=UTF-8");
- }
-
- var inst = this;
- req.onreadystatechange = function ORSC(aEvent) {
- inst._logger.debug("._call() ReadyState: " + req.readyState);
-
- if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
- try {
- inst._logger.debug("._call() Status: " + req.status);
-
- if (req.status/100 == 2) {
- inst._logger.debug("._call() response:\n" + req.responseText);
- var s = new CU.Sandbox("about:blank");
- var result = CU.evalInSandbox("(" + req.responseText + ")", s);
-
- if (result) {
- aListener.onSuccess(result);
- } else {
- inst._logger.debug("._call() error: no result");
- aListener.onError();
- }
-
- } else if (req.status == 401) {
- // XXX: Workaround for 401 errors we're seeing with Twitter.
- // Loop call if necessary up to TWITTER_MAX_AUTH_ATTEMPTS times.
-
- // Increment counter
- var count;
- if (aCount == null) {
- count = 1;
- } else {
- count = aCount + 1;
- }
-
- if (count < TWITTER_MAX_AUTH_ATTEMPTS) {
- // Try, try again
- inst._logger.debug("._call() Got a 401. Will try "
- + (TWITTER_MAX_AUTH_ATTEMPTS - count)
- + " more time(s).");
- inst._call(aListener, aMethod, aParams, aRequestType, aPostVars,
- count);
- } else {
- // Ok. Give up.
- inst._logger.debug("._call() HTTP error");
- aListener.onError(/*req.status*/);
- }
-
- } else {
- // HTTP errors
- inst._logger.debug("._call() HTTP error");
- aListener.onError(/*req.status*/);
- }
- } catch (ex) {
- // XMLHTTPERROR (connection lost)
- inst._logger.debug("._call() exception: " + ex);
- aListener.onError(/*inst.getHTTPError("9001")*/);
- }
- }
- }
-
- var postBody = "";
- if (aPostVars) {
- for (var v in aPostVars) {
- if (postBody.length) {
- postBody += "&";
- }
- postBody += v + "=" + encodeURIComponent(aPostVars[v]);
- }
- }
-
- if ((requestType == "POST") && postBody && postBody.length) {
- this._logger.debug("._call() postBody: " + postBody);
- req.send(postBody);
- } else {
- req.send(null);
- }
- }
-
-
- /**
- * Get info (including status) about a user.
- * @param aUid Twitter uid of the user to query.
- * @param aListener
- */
- flockTwitterAPI.prototype.userShow =
- function api_userShow(aUid, aListener) {
- this._logger.debug(".userShow(" + aUid + ", aListener)");
-
- var url = "users/show/" + aUid;
- gApi._call(aListener, url, null, "GET", null, null);
- }
-
-
- /**
- * direct_messages
- * Returns a list of the 20 most recent direct messages sent to the
- * authenticating user. The XML and JSON versions include detailed
- * information about the sending and recipient users.
- *
- * URL: http://twitter.com/direct_messages.format
- * Formats: xml, json, rss, atom
- * Parameters:
- * since Optional. Narrows the resulting list of direct messages to just
- * those sent after the specified HTTP-formatted date. The same
- * behavior is available by setting the If-Modified-Since parameter
- * in your HTTP request.
- * Ex: http://twitter.com/direct_messages.atom?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
- * since_id Optional. Returns only direct messages with an ID greater than
- * (that is, more recent than) the specified ID.
- * Ex: http://twitter.com/direct_messages.xml?since_id=12345
- * page Optional. Retrieves the 20 next most recent direct messages.
- * Ex: http://twitter.com/direct_messages.xml?page=3
- */
- flockTwitterAPI.prototype.directMessages =
- function api_directMessages(aSince, aSinceId, aPage, aListener) {
- this._logger.debug(".directMessages(" + aSince + ", "
- + aSinceId + ", "
- + aPage + ", "
- + "aListener)");
- var params = {};
-
- if (aSince) {
- params.since = aSince;
- }
-
- if (aSinceId) {
- params.since_id = aSinceId;
- }
-
- if (aPage) {
- params.page = aPage;
- }
-
- var url = "direct_messages";
- gApi._call(aListener, url, params, "GET", null, null);
- }
-
-
- /**
- * Get a count of all messages for the authenticated user
- * @param aListener
- */
- flockTwitterAPI.prototype.getTotalMessageCount =
- function api_getTotalMessageCount(aListener) {
- this._logger.debug(".getTotalMessageCount(aListener)");
-
- // We start off by asking for page 1 of messages.
- var page = 1;
-
- // Message counter
- var count = 0;
-
- var inst = this;
- var getMessagesListener = {
- onSuccess: function L_onSuccess(aResult) {
- // If a full page is returned then go grab another
- inst._logger.debug("Twitter messages returned: " + aResult.length);
- count += aResult.length;
- if (aResult.length == TWITTER_MESSAGES_PAGE_SIZE) {
- inst._logger.debug("Fetching more messages");
- page++;
- inst.directMessages(null, null, page, getMessagesListener);
- } else {
- inst._logger.debug("Fetching messages complete");
- aListener.onSuccess(count);
- }
- },
- onError: function L_onError(aError) {
- inst._logger.debug(".getMessagesListener() error: " + aError);
- }
- }
-
- this.directMessages(null, null, page, getMessagesListener);
- }
-
-
- /**
- * Get a user's friends' updates.
- * @param aUid Twitter uid of the user whose friends to view or null to
- * view the currently authenticated user's friends.
- * @param aListener
- */
- flockTwitterAPI.prototype.getFriendsStatus =
- function api_getFriendsStatus(aUid, aListener) {
- this._logger.debug(".getFriendsStatus(" + aUid + ", aListener)");
-
- var peopleHash = {};
- var inst = this;
-
- var params = {};
- // We start off by asking for page 1 of friends.
- params.page = 1;
-
- var getFriendsListener = {
- onSuccess: function L_onSuccess(aResult) {
- for (var i in aResult) {
- var id = aResult[i].id;
- peopleHash[id] = aResult[i];
-
- // If there is no status item then stub one in
- if (!peopleHash[id].status) {
- peopleHash[id].status = {
- created_at: 0,
- text: "",
- id: null
- }
- }
-
- inst._logger.debug("Got Twitter person: " + peopleHash[id].name);
- }
-
- // If a full page is returned then go grab another
- inst._logger.debug("Twitter friends returned: " + aResult.length);
- if (aResult.length == TWITTER_FRIENDS_PAGE_SIZE) {
- params.page++;
- inst._logger.debug("Fetching more friends, now getting page "
- + params.page);
- gApi._call(getFriendsListener, url, params, "GET", null, null);
- } else {
- inst._logger.debug("Fetching friends complete");
- aListener.onSuccess(peopleHash);
- }
- },
- onError: function L_onError(aError) {
- inst._logger.debug(".getFriendsStatus() error: " + aError);
- }
- }
-
- var url = "statuses/friends";
- if (aUid) {
- url += "/" + aUid;
- }
- gApi._call(getFriendsListener, url, null, "GET", null, null);
- }
-
-
- /**
- * Set the user's status
- * @param aStatusMessage a string containing the message to set
- * @param aListener
- *
- * Notes from Twitter API documentation:
- * -----------------------------------------------------------------------
- * Request must be a POST.
- * URL: http://twitter.com/statuses/update.format
- * Formats: xml, json.
- * Returns the posted status in requested format when successful.
- * Parameters:
- * status Required The text of your status update.
- * Be sure to URL encode as necessary.
- * Must not be more than 160 characters and should not
- * be more than 140 characters to ensure optimal display
- */
- flockTwitterAPI.prototype.setStatus =
- function api_setStatus(aStatusMessage, aListener) {
- this._logger.debug(".setStatus(" + aStatusMessage + ", aListener)");
-
- // substring() starts at 0 while TWITTER_MAX_STATUS_LENGTH counts from 1.
- var message = aStatusMessage.substring(0, (TWITTER_MAX_STATUS_LENGTH - 1));
-
- var postVars = {
- "source": "flock", // This value specified by Alex Payne at Twitter.
- "status": message
- };
- var url = "statuses/update";
- gApi._call(aListener, url, null, "POST", postVars, null);
- }
-
-
- /*************************************************************************
- * Component: flockTwitterAccount
- *************************************************************************/
-
- function flockTwitterAccount() {
- FlockSvcUtils.getLogger(this);
- this._logger.init("twitterAccount");
-
- FlockSvcUtils.getACUtils(this);
- FlockSvcUtils.getCoop(this);
- // XXX: I should be able to use FlockSvcUtils.getWD() here, but it can
- // only be called by the service.
- this._WebDetective = CC["@flock.com/web-detective;1"]
- .getService(CI.flockIWebDetective);
-
- FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "deactivate");
- FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "login");
- FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "logout");
- FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod(this, "remove");
-
- FlockSvcUtils.flockISocialWebServiceAccount.addDefaultMethod(this, "getFriendCount");
-
- var sbs = CC["@mozilla.org/intl/stringbundle;1"]
- .getService(CI.nsIStringBundleService);
- this._bundle = sbs.createBundle(TWITTER_PROPERTIES);
- }
-
-
- /*************************************************************************
- * flockTwitterAccount: XPCOM Component Creation
- *************************************************************************/
-
- // Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
- // but do NOT add this component to the list of constructors passed to
- // FlockXPCOMUtils.genericModule().
- flockTwitterAccount.prototype = new FlockXPCOMUtils.genericComponent(
- CLASS_NAME + " Account",
- "",
- "",
- flockTwitterAccount,
- 0,
- [
- CI.flockIWebServiceAccount,
- CI.flockISocialWebServiceAccount
- ]
- );
-
-
- /*************************************************************************
- * flockTwitterAccount: flockIWebServiceAccount Implementation
- *************************************************************************/
-
- // readonly attribute AString urn;
- flockTwitterAccount.prototype.urn = "";
-
- // void activate(in flockIListener aListener);
- flockTwitterAccount.prototype.activate =
- function fta_activate(aListener) {
- this._logger.debug(".activate()");
- var acctCoopObj = this._coop.get(this.urn);
- acctCoopObj.isAuthenticated = true;
- acctCoopObj.isPollable = true;
-
- if (aListener) {
- aListener.onSuccess(this, "activate");
- }
- }
-
- // DEFAULT: void deactivate(in flockIListener aListener);
- // DEFAULT: void login(in flockIListener aListener);
- // DEFAULT: void logout(in flockIListener aListener);
-
- // void keep();
- flockTwitterAccount.prototype.keep =
- function fta_keep() {
- this._logger.debug(".keep()");
- this._coop.get(this.urn).isTransient = false;
- var urn = this.urn.replace("account:", "service:").replace("flock:", "");
- this._acUtils.makeTempPasswordPermanent(urn);
- }
-
- // DEFAULT: void remove();
-
-
- /*************************************************************************
- * flockTwitterAccount: flockISocialWebServiceAccount Implementation
- *************************************************************************/
- // readonly attribute boolean hasFriendActions;
- flockTwitterAccount.prototype.hasFriendActions = true;
-
- // readonly attribute boolean isPostLinkSupported;
- flockTwitterAccount.prototype.isPostLinkSupported = true;
-
- // readonly attribute boolean isMyMediaFavoritesSupported;
- flockTwitterAccount.prototype.isMyMediaFavoritesSupported = false;
-
- // readonly attribute boolean isStatusSupported;
- flockTwitterAccount.prototype.isStatusSupported = true;
-
- // readonly attribute boolean isStatusEditable;
- flockTwitterAccount.prototype.isStatusEditable = true;
-
-
- // AString formatStatusForDisplay(in AString aStatusMessage);
- flockTwitterAccount.prototype.formatStatusForDisplay =
- function fma_formatStatusForDisplay(aStatusMessage) {
- this._logger.debug(".formatStatusForDisplay(" + aStatusMessage + ")");
-
- var message = (aStatusMessage) ? aStatusMessage : "";
-
- // Responses from Twitter contain these HTML entities. Order is important!
- message = message.replace(/&/g, "&")
- .replace(/>/g, ">")
- .replace(/</g, "<")
- .replace(/"/g, '"');
- return message;
- }
-
-
- // AString getProfileURLForFriend(in AString aFriendUrn);
- flockTwitterAccount.prototype.getProfileURLForFriend =
- function fta_getProfileURLForFriend(aFriendUrn) {
- this._logger.debug(".getProfileURLForFriend('" + aFriendUrn + "')");
-
- var url = "";
- var coopFriend = this._coop.get(aFriendUrn);
- var screenName = coopFriend.screenName;
-
- if (screenName) {
- url = this._WebDetective.getString(CLASS_SHORT_NAME, "friendProfile", "")
- .replace("%screenName%", screenName);
- }
-
- this._logger.debug(".getProfileURLForFriend() url: " + url);
- return url;
- }
-
-
- // void setStatus(in AString aStatusMessage, in flockIListener aListener);
- flockTwitterAccount.prototype.setStatus =
- function fma_setStatus(aStatusMessage, aListener) {
- this._logger.debug(".setStatus(" + aStatusMessage + ")");
-
- var inst = this;
- var statusListener = {
- onSuccess: function L_onSuccess(aResult) {
- // If the API call succeeded, also set the coop.Account status.
- inst._coop.get(inst.urn).statusMessage = aStatusMessage;
- if (aListener) {
- aListener.onSuccess(aResult, null);
- }
- },
- onError: function L_onError(aError) {
- if (aListener) {
- aListener.onError(null, null, aError);
- }
- }
- }
- gApi.setStatus(aStatusMessage, statusListener);
- }
-
-
- // AString getEditableStatus();
- flockTwitterAccount.prototype.getEditableStatus =
- function fma_getEditableStatus() {
- this._logger.debug(".getEditableStatus()");
- var message = this._coop.get(this.urn).statusMessage;
- return this.formatStatusForDisplay(message);
- }
-
-
- // AString getMeNotifications();
- flockTwitterAccount.prototype.getMeNotifications =
- function fma_getMeNotifications() {
- this._logger.debug(".getMeNotifications()");
-
- var noties = [];
- var inst = this;
- function _addNotie(aType, aCount) {
- var stringName = "flock." + CLASS_SHORT_NAME + ".noties." + aType + "."
- + ((parseInt(aCount) <= 0) ? "none" : "some");
-
- inst._logger.debug("aType: " + aType
- + " aCount: " + aCount
- + " name: " + stringName);
- noties.push({
- class: aType,
- tooltip: inst._bundle.GetStringFromName(stringName),
- count: aCount,
- URL: inst._WebDetective.getString(CLASS_SHORT_NAME, aType + "_URL", "")
- });
- }
- var coopAccount = this._coop.get(this.urn);
- _addNotie("meMessages", coopAccount.accountMessages);
-
- return JSON.toString(noties);
- }
-
- flockTwitterAccount.prototype.markAllMeNotificationsSeen =
- function flockTwitterAccount_markAllMeNotificationsSeen(aType) {
- this._logger.debug(".markAllMeNotificationsSeen('" + aType + "')");
- var c_acct = this._coop.get(this.urn);
- switch (aType) {
- case "meMessages":
- c_acct.accountMessages = 0;
- break;
- default:
- break;
- }
- }
-
- // AString getFriendActions(in AString aFriendUrn);
- flockTwitterAccount.prototype.getFriendActions =
- function fma_getFriendActions(aFriendUrn) {
- this._logger.debug(".getFriendActions('" + aFriendUrn + "')");
-
- var actionNames = ["friendMessage",
- "friendNudge",
- "friendViewProfile"];
-
- var actions = [];
- var coopFriend = this._coop.get(aFriendUrn);
- if (coopFriend) {
- var coopAccount = this._coop.get(this.urn);
- for each (var action in actionNames) {
- actions.push({
- label: this._bundle.GetStringFromName("flock."
- + CLASS_SHORT_NAME
- + ".actions." + action),
- class: action,
- spec: this._WebDetective.getString(CLASS_SHORT_NAME, action, "")
- .replace("%accountid%", coopAccount.accountId)
- .replace("%friendid%", coopFriend.accountId)
- });
- }
- }
-
- return JSON.toString(actions);
- }
-
-
- // AString getSharingAction(in AString aFriendUrn,
- // in nsITransferable aTransferable);
- flockTwitterAccount.prototype.getSharingAction =
- function fma_getSharingAction(aFriendUrn, aTransferable) {
- this._logger.debug(".getSharingAction('" + aFriendUrn + "')");
-
- var sharingAction = "";
- var coopFriend = this._coop.get(aFriendUrn);
- if (!coopFriend) {
- return sharingAction;
- }
-
- var flavours = ["text/x-flock-media",
- "text/x-moz-url",
- "text/unicode"];
-
- var message = CC[FLOCK_RICH_DND_CONTRACTID]
- .getService(CI.flockIRichDNDService)
- .getMessageFromTransferable(aTransferable,
- flavours.length,
- flavours);
- if (!message.body) {
- return sharingAction;
- }
-
- sharingAction = this._WebDetective
- .getString(CLASS_SHORT_NAME,
- "shareAction_directMessage", "")
- .replace("%friendid%", coopFriend.accountId)
- .replace("%message%", encodeURIComponent(message.body));
-
- this._logger.debug(".getSharingAction(): " + sharingAction);
- return sharingAction;
- }
-
-
- flockTwitterAccount.prototype.getPostLinkAction =
- function fta_getPostLinkAction(aTransferable) {
- var postLinkAction = "";
- var url = "";
-
- if (aTransferable) {
- // Something was dropped onto the "Post Link" button: get the URL from the
- // transferable
- var flavours = ["text/x-flock-media",
- "text/x-moz-url",
- "text/unicode"];
-
- var message = CC[FLOCK_RICH_DND_CONTRACTID]
- .getService(CI.flockIRichDNDService)
- .getMessageFromTransferable(aTransferable,
- flavours.length,
- flavours);
-
- url = message.body;
- } else {
- // The "Post Link" button was clicked: get the current tab's URL
- var win = CC["@mozilla.org/appshell/window-mediator;1"]
- .getService(CI.nsIWindowMediator)
- .getMostRecentWindow("navigator:browser");
- if (win) {
- url = win.gBrowser.currentURI.spec;
- }
- }
-
- if (url) {
- postLinkAction = this._WebDetective
- .getString(CLASS_SHORT_NAME,
- "shareAction_publicMessage",
- "")
- .replace("%message%", encodeURIComponent(url));
- }
-
- return postLinkAction;
- }
-
-
- /*************************************************************************
- * flockTwitterAccount Private Data and Functions
- *************************************************************************/
-
-
- /*************************************************************************
- * XPCOM Support - Module Construction
- *************************************************************************/
-
- // Create array of components.
- var componentsArray = [flockTwitterService];
-
- // Generate a module for XPCOM to find.
- var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
- componentsArray);
-